package com.carlteam.manager;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;

import com.carlteam.dto.CtrlMessage;
import com.carlteam.listener.OnClientJoinListener;
import com.carlteam.listener.OnClientLeaveListener;
import com.carlteam.model.Client;
import com.carlteam.utils.ObjectUtils;

public class ClientMgm {
	private static final int UDP_CLIENTMGM_PORT = 15151;
	private static final int PKG_MAX_DATA_LEN = 512; // UDP包的数据长度
	private static final int KEEP_ALIVE_INTERVAL = 3000; // 保持连接所需的包的发送间隔
	
	private static final int MSG_WHO_IS_ONLINE = 0x001; // 自身上线消息
	private static final int MSG_KEEP_ALIVE = 0x002; 	// 连接维持消息
	private static final int MSG_I_AM_ONLINE = 0x003; 	// 自身上线消息的反馈消息

	private static ClientMgm mSelf = null;

	private CopyOnWriteArrayList<OnClientJoinListener> mClientJoinListeners = null;
	private CopyOnWriteArrayList<OnClientLeaveListener> mClientLeaveListeners = null;
	private CopyOnWriteArrayList<ClientTicker> mOnlineClients = null;

	private DatagramSocket mSocket = null;
	
	private MessageReceiveThread mMessageReceiveThread = null;
	private KeepAliveThread mKeepAliveThread = null;

	private String myTag = null;

	private ClientMgm() {
		try {
			mSocket = new DatagramSocket(UDP_CLIENTMGM_PORT);
			mSocket.setBroadcast(true);
			mClientJoinListeners = new CopyOnWriteArrayList<OnClientJoinListener>();
			mClientLeaveListeners = new CopyOnWriteArrayList<OnClientLeaveListener>();
			mOnlineClients = new CopyOnWriteArrayList<ClientTicker>();
			mMessageReceiveThread = new MessageReceiveThread();
			mKeepAliveThread = new KeepAliveThread();
		} catch (SocketException e) {
			e.printStackTrace();
		}
	}

	/*
	 * 以下Singleton写法会导致多线程场景下报NullPointer异常，但是这里考虑
	 * 到所有的Mgm类都没有多线程使用场景，所以没有进行线程安全处理
	 */
	public static ClientMgm getInstance() {
		if (mSelf == null)
			mSelf = new ClientMgm();
		return mSelf;
	}

	
	/*
	 * 广播上线消息到子网
	 */
	public void enterNetworkWithTag(String myTag) {
		this.myTag = myTag;
		if (!mMessageReceiveThread.isAlive()) mMessageReceiveThread.start();
		if (!mKeepAliveThread.isAlive()) mKeepAliveThread.start();
		CtrlMessage msg = new CtrlMessage();
		msg.setMsgId(MSG_WHO_IS_ONLINE);
		msg.setExtraObj(myTag);
		broadcastMessage(msg);
	}
	
	/*
	 * 有客户端离开的话要主动调用这个函数通知其它客户端自己已离开
	 */
	public void leaveNetwork() {
		if (mMessageReceiveThread.isAlive()) mMessageReceiveThread.stop();
		if (mKeepAliveThread.isAlive()) mKeepAliveThread.stop();
	}
	
	/*
	 * 向某个ip的客户端发送消息
	 */
	public void sendMessageToClient(String ip, CtrlMessage msg) {
		byte[] pkgBytes = ObjectUtils.ObjectToByteArray(msg);
		try {
			DatagramPacket pack = new DatagramPacket(pkgBytes, pkgBytes.length, InetAddress.getByName(ip), UDP_CLIENTMGM_PORT);
			mSocket.send(pack);
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/*
	 * 向网络广播消息
	 */
	public void broadcastMessage(CtrlMessage msg) {
		byte[] pkgBytes = ObjectUtils.ObjectToByteArray(msg);
		try {
			DatagramPacket pack = new DatagramPacket(pkgBytes, pkgBytes.length, InetAddress.getByName("255.255.255.255"), UDP_CLIENTMGM_PORT);
			mSocket.send(pack);
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	class ClientTicker {
		private Client mClient = null; // 记录Client信息
		private int mInitTick; // 维持在线的计数器的初始值
		private int mAliveTick; // 用来维持在线的统计变量，该变量被down到0的时候表示该用户已下线

		public ClientTicker(Client client, int initTick) {
			mClient = client;
			mInitTick = initTick;
		}

		public Client getClient() {
			return mClient;
		}

		public void setClient(Client client) {
			this.mClient = mClient;
		}

		public int countDownTick() {
			if (this.mAliveTick > 0)
				--this.mAliveTick;
			return this.mAliveTick;
		}

		public void resetTick() {
			this.mAliveTick = this.mInitTick;
		}
	}

	class KeepAliveThread extends Thread {
		@Override
		public void run() {
			CtrlMessage msg = new CtrlMessage();
			msg.setMsgId(MSG_KEEP_ALIVE);
			msg.setExtraObj(myTag);
			while (true) {
				try {
					broadcastMessage(msg);
					this.sleep(KEEP_ALIVE_INTERVAL);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

	class MessageReceiveThread extends Thread {
		private byte[] mPkgBytes = null;
		private DatagramPacket mPacket = null;

		public MessageReceiveThread() {
			mPkgBytes = new byte[PKG_MAX_DATA_LEN];
			mPacket = new DatagramPacket(mPkgBytes, PKG_MAX_DATA_LEN);
		}

		@Override
		public void run() {
			while (true) {
				try {
					mSocket.receive(mPacket);
					String remoteIp = mPacket.getAddress().getHostAddress();
					CtrlMessage remoteMsg = (CtrlMessage) ObjectUtils
							.ByteArrayToObject(mPacket.getData());
					switch (remoteMsg.getMsgId()) {
					case MSG_WHO_IS_ONLINE: {
						System.out.println("Client: " + remoteIp + " in.");
						// 扫描并更新在线Client列表
						scanClientTickerList(remoteIp, (String) remoteMsg.getExtraObj());
						// 回复自己的信息
						CtrlMessage msg = new CtrlMessage();
						msg.setMsgId(MSG_I_AM_ONLINE);
						msg.setExtraObj(myTag);
						sendMessageToClient(remoteIp, msg);
						break;
					}
					case MSG_KEEP_ALIVE: {
						System.out.println("Client: [" + (String)remoteMsg.getExtraObj() + "] " + remoteIp + " still in.");
						scanClientTickerList(remoteIp, (String) remoteMsg.getExtraObj());
						break;
					}
					case MSG_I_AM_ONLINE: {
						Client newOne = new Client();
						newOne.setIp(remoteIp);
						newOne.setTag((String) remoteMsg.getExtraObj());
						mOnlineClients.add(new ClientTicker(newOne, 2));
						for (Iterator<OnClientJoinListener> it = mClientJoinListeners.iterator(); it.hasNext();) {
							OnClientJoinListener listener = it.next();
							listener.OnClientJoin(newOne);
						}
						break;
					}
					default:
						break;
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

		private void scanClientTickerList(String aliveIp, String tag) {
			boolean isIpOnline = false;
			Iterator<ClientTicker> it = mOnlineClients.iterator();
			while (it.hasNext()) {
				ClientTicker currTicker = it.next();
				if (currTicker.countDownTick() == 0) {
					Iterator<OnClientLeaveListener> it2 = mClientLeaveListeners.iterator();
					while(it2.hasNext()) {
						OnClientLeaveListener listener = it2.next();
						listener.OnClientLeave(currTicker.getClient());
					}
					mOnlineClients.remove(it);
				} else {
					if (aliveIp.equals(currTicker.getClient().getIp())) {
						currTicker.resetTick();
						currTicker.getClient().setTag(tag);
						isIpOnline = true;
					}
				}
			}
			Client newOne = new Client();
			newOne.setIp(aliveIp);
			newOne.setTag(tag);
			// 2表示Ticker的初始值为2, 即延迟超过TIMEOUT_INTERVAL * 2即判定下线
			if (!isIpOnline) mOnlineClients.add(new ClientTicker(newOne, 2));
		}
	}
}
